package sublinear.model.db;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Logger;

import sublinear.common.SimplePRNG;
import sublinear.model.AbstractGraph;

public class AdjacencyList extends AbstractGraph
{
	
	private static final Logger log = Logger.getLogger( AdjacencyList.class.getName() );

	public static class AdjacencyListNode 
	{
		
		public AdjacencyListNode(Long id, String title) {
			node = new GraphNode(id, title);
			neighborIds = new ArrayList<Long>(0);
		}
		
		GraphNode node;
		ArrayList<Long> neighborIds;
		
	}
	
	private HashMap<Long, AdjacencyListNode> list;
	private ArrayList<Long> keys;
	private long maxDegree;
	
	public AdjacencyList() {
		list = new HashMap<Long, AdjacencyListNode>();
		keys = new ArrayList<Long>();
		maxDegree = 0;
	}
	
	public void addArticle(Long id, String title) {
		log.finest("Added article " + title + " (" + id + ")");
		
		list.put(id, new AdjacencyListNode(id, title));
		keys.add(id);
	}
	
	public void addLink(Long id, Long destId) {
		if (!list.containsKey(id)) {
			throw new NoSuchElementException("Tried to add link to article ID " + id + ", but it was not found in list");
		}
		
		if (!list.containsKey(destId)) {
			log.warning("Tried to add outgoing link to non-existing articles - " + id + " => " + destId);
			return;
		}
		
		AdjacencyListNode node = list.get(id);
		node.neighborIds.add(destId);
		if (node.neighborIds.size() > maxDegree) {
			maxDegree = node.neighborIds.size();
		}
	}
	
	public boolean addLinkIfValid(Long id, Long destId) {
		if (list.containsKey(id) && list.containsKey(destId)) {
			AdjacencyListNode node = list.get(id);
			node.neighborIds.add(destId);
			return true;
		} else {
			log.finest("Link from or to non-existing articles - " + id + " => " + destId + " - ignore");
			return false;
		}
	}

	@Override
	public ArrayList<GraphNode> getNeighbors(GraphNode node) {
		Long id = node.getUniqueId();
		
		if (!list.containsKey(id)) {
			throw new NoSuchElementException("Article ID " + id + " was not found in list");
		}
		
		ArrayList<GraphNode> neighbors = new ArrayList<GraphNode>();
		for (Long nId : list.get(id).neighborIds) {
			neighbors.add(getGraphNode(nId));
		}
		
		return neighbors;
	}

	@Override
	public GraphNode getRandomNode() {
		int randomEntry = SimplePRNG.getInstance().nextInt(keys.size());
		long randomId = keys.get(randomEntry);
		
		return getGraphNode(randomId);
	}
	
	@Override
	public long getDegree(GraphNode node)
	{
		if (!list.containsKey(node.getUniqueId())) {
			throw new NoSuchElementException("Article ID " + node.getUniqueId() + " was not found in list");
		}

		return list.get(node.getUniqueId()).neighborIds.size();
	}
	
	public GraphNode getGraphNode(long id) {
		if (!list.containsKey(id)) {
			throw new NoSuchElementException("Article ID " + id + " was not found in list");
		}
		
		return list.get(id).node;
	}
	
	public long getMaxDegree() {
		return maxDegree;
	}
	
	public int getAverageDegree() {
		float averageDegreeTmp = 0.0f;
		float size = (float) list.size();
		for (Long id : list.keySet()) {
			averageDegreeTmp += (list.get(id).neighborIds.size() / size);
		}
		return Math.round(averageDegreeTmp);
	}
	
	public Set<Long> getNodeIds() {
		return list.keySet();
	}

	@Override
	public long getSize() {
		return list.size();
	}

}
